package structures;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Scanner;
import java.util.Set;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

public class AutomateBuhi {
	public State startState;

	public AutomateBuhi(File fWA) throws ParserConfigurationException, SAXException, IOException {
		loadAutomateFromFile(fWA);
		startState.isStartState = true;
	}

	public AutomateBuhi(State startState) {
		this.startState = startState;
		startState.isStartState = true;
	}

	public static AutomateBuhi loadFromFormulaConvertion(String automata) {
		BufferedReader br = new BufferedReader(new StringReader(automata));
		try {
			br.readLine();
		} catch (IOException e1) {
			e1.printStackTrace();
		}
		List<String> states = new LinkedList<String>();
		String currentState = "";
		try {
			while (br.ready()) {
				String s = br.readLine();
				if (currentState == "")
					currentState += s;
				else
					currentState += '\n' + s;

				if (s.contains(";")) {
					states.add(currentState);
					currentState = "";
				}
				if (s.contains("skip")) {
					states.add(currentState);
					break;
				}
			}
		} catch (IOException e) {
			e.printStackTrace();
		}

		Object[] tmpstatesArray = states.toArray();
		String[] statesArray = new String[tmpstatesArray.length];
		for (int i = 0; i != tmpstatesArray.length; ++i) {
			String s = (String) tmpstatesArray[i];
			s = s.replaceAll("goto", "");
			s = s.replaceAll("if", "");
			s = s.replaceAll("fi;", "");
			s = s.replaceAll("->", "-");
			statesArray[i] = s.replaceAll("\\s+", "");
		}

		HashMap<String, Integer> names = new HashMap<String, Integer>();
		for (int i = 0; i != statesArray.length; ++i) {
			Scanner read = new Scanner(statesArray[i]);
			read.useDelimiter(":");
			names.put(read.next(), i);
		}

		State[] resultingStates = new State[statesArray.length];
		for (int i = 0; i != resultingStates.length; ++i) {
			resultingStates[i] = new State();
		}

		resultingStates[resultingStates.length - 1].addEdge(resultingStates[resultingStates.length - 1], "1");

		for (int i = 0; i != resultingStates.length; ++i) {
			Scanner read = new Scanner(statesArray[i]);
			read.useDelimiter(":");
			int element = 0;
			while (read.hasNext()) {
				++element;
				String next = read.next();
				if (element == 1)
					if (next.contains("accept"))
						resultingStates[i].setFinalState(true);
				if (next.isEmpty())
					continue;
				if (next.contains("skip"))
					break;
				if (next.charAt(0) != '(')
					continue;
				Scanner read2 = new Scanner(next);
				read2.useDelimiter("-");
				String value = read2.next();
				value = value.replaceAll("\\(", "");
				value = value.replaceAll("\\)", "");
				String name = read2.next();
				resultingStates[i].addEdge(resultingStates[names.get(name)], value);
			}
		}

		resultingStates[0].isStartState = true;
		return new AutomateBuhi(resultingStates[0]);
	}

	public AutomateBuhi intersectWith(AutomateBuhi automate) {
		ArrayList<State> result = new ArrayList<State>();
		HashMap<String, Integer> names = new HashMap<String, Integer>();

		State[] automate1 = this.startState.getStatesArray();
        System.err.println(automate1.length);
		State[] automate2 = automate.startState.getStatesArray();
        System.err.println(automate2.length);
        Arrays.sort(automate1);
        Arrays.sort(automate2);
        
		for (int i = 0; i < automate1.length; i++) {
			automate1[i].index = i;
		}
		for (int i = 0; i < automate2.length; i++) {
			automate2[i].index = i;
		}

		names.put("r0q0_0", 0);
		result.add(new State());

		for (int au1 = 0; au1 < automate1.length; au1++) {
			for (int au2 = 0; au2 < automate2.length; au2++) {
				Object[] rEdges = automate1[au1].getEdges().entrySet().toArray();
				Object[] qEdges = automate2[au2].getEdges().entrySet().toArray();
				for (int r = 0; r < rEdges.length; r++) {
					Map.Entry<State, Set<String>> pair1 = (Map.Entry<State, Set<String>>) rEdges[r];
					for (int q = 0; q < qEdges.length; q++) {
						Map.Entry<State, Set<String>> pair2 = (Map.Entry<State, Set<String>>) qEdges[q];
						Set<String> set1 = pair1.getValue();
						Set<String> set2 = pair2.getValue();
						Set<String> resultingSet = new HashSet<String>();
						resultingSet.addAll(set1);												
						resultingSet.retainAll(set2);
						for (String s1: set1){
							if (s1.equals("1")){
								resultingSet.addAll(set2);
							}
						}
						for (String s2: set2){
							if (s2.equals("1")){
								resultingSet.addAll(set1);
							}
						}
						if (resultingSet.size() > 1){
							System.err.println("ERROR");
						}
						Object[] array = resultingSet.toArray();
						if (array.length == 0)
							continue;
						String resultingString = (String) array[0];
						for (int i = 1; i < array.length; i++) {
							System.err.println("ERROR");
							resultingString += "||" + array[i];
						}

						for (int x = 0; x < 3; x++) {
							Integer left = names.get("r" + au1 + "q" + au2 + "_" + x);
							if (left == null) {
								left = names.size();
								names.put("r" + au1 + "q" + au2 + "_" + x, left);
								result.add(new State());
							}
							int y = x;
							if ((x == 0) && (pair1.getKey().isFinalState())) {
								y = 1;
							} else {
								if ((x == 1) && (pair2.getKey().isFinalState())) {
									y = 2;
								} else {
									if (x == 2) {
										y = 0;
									}
								}
							}
							Integer right = names.get("r" + pair1.getKey().index + "q" + pair2.getKey().index + "_" + y);
							if (right == null) {
								right = names.size();
								names.put("r" + pair1.getKey().index + "q" + pair2.getKey().index + "_" + y, right);
								result.add(new State());
							}
							result.get(left).addEdge(result.get(right), resultingString);
						}
					}
				}
			}
		}

		for (String key : names.keySet()) {
			if (key.contains("_2")) {
				result.get(names.get(key)).setFinalState(true);
			}
		}

		return new AutomateBuhi(result.get(0));
	}

	public void loadAutomateFromFile(File fileWithAutomate) throws ParserConfigurationException, SAXException, IOException {
		List<XMLState> states = new ArrayList<XMLState>();
		List<XMLEdge> edges = new ArrayList<XMLEdge>();

		DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
		DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
		Document doc = dBuilder.parse(fileWithAutomate);
		doc.getDocumentElement().normalize();
		NodeList allWidgets = doc.getElementsByTagName("widget");
		for (int nodeId = 0; nodeId < allWidgets.getLength(); nodeId++) {
			Node curWidget = allWidgets.item(nodeId);
			String type = ((Element) curWidget).getAttribute("type");
			if (type.equals("State")) {
				int stateId = Integer.parseInt(((Element) curWidget).getAttribute("id"));
				states.add(new XMLState(stateId, Integer.parseInt((((Element) ((Element) curWidget).getElementsByTagName("attributes").item(0))).getElementsByTagName("type").item(0).getTextContent())));
				NodeList inc = (((Element) ((Element) curWidget).getElementsByTagName("attributes").item(0))).getElementsByTagName("incoming");

				NodeList out = (((Element) ((Element) curWidget).getElementsByTagName("attributes").item(0))).getElementsByTagName("outgoing");
				indexloop: for (int index = 0; index < inc.getLength(); index++) {
					int id = Integer.parseInt(((Element) inc.item(index)).getAttribute("id"));
					for (XMLEdge edge : edges) {
						if (edge.getId() == id) {
							edge.setTo(stateId);
							continue indexloop;
						}
					}
					XMLEdge edge = new XMLEdge(id);
					edge.setTo(stateId);
					edges.add(edge);
				}

				indexloop2: for (int index = 0; index < out.getLength(); index++) {
					int id = Integer.parseInt(((Element) out.item(index)).getAttribute("id"));
					for (XMLEdge edge : edges) {
						if (edge.getId() == id) {
							edge.setFrom(stateId);
							continue indexloop2;
						}
					}
					XMLEdge edge = new XMLEdge(id);
					edge.setFrom(stateId);
					edges.add(edge);
				}
			}

			if (type.equals("Transition")) {
				int edgeId = Integer.parseInt(((Element) curWidget).getAttribute("id"));
				XMLEdge curEdge = null;
				for (XMLEdge edge : edges) {
					if (edge.getId() == edgeId) {
						curEdge = edge;
						break;
					}
				}
				if (curEdge == null) {
					curEdge = new XMLEdge(edgeId);
					edges.add(curEdge);
				}
				curEdge.addAction((((Element) ((Element) curWidget).getElementsByTagName("attributes").item(0))).getAttribute("guard"));
				
//				NodeList actions = (((Element) ((Element) curWidget).getElementsByTagName("attributes").item(0))).getElementsByTagName("action");
//
//				for (int index = 0; index < actions.getLength(); index++) {
//					curEdge.addAction(((Element) actions.item(index)).getAttribute("name"));
//				}

			}
		}
		Map<Integer, State> allStates = new HashMap<Integer, State>();
		for (XMLState state : states) {
			allStates.put(state.getId(), new State());
			if (state.getType() == 1) {
				startState = allStates.get(state.getId());
			}
			if (state.getType() == 2) {
				allStates.get(state.getId()).setFinalState(true);
			}
		}
		for (XMLEdge edge : edges) {
			for (String act : edge.getAction()) {
				allStates.get(edge.getFrom()).addEdge(allStates.get(edge.getTo()), act);
			}
		}
	}

}
